/*-
 * Scala language model.
 *
 * Copyright (c) 2010 Alex Suhan
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer
 * in this position and unchanged.
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.util.Set;
import java.util.HashMap;
import java.util.LinkedList;

class ScalaMethodInfo implements ScalaFeatureInfo
{
    private String returnedType;
    private ScalaFormals formalParams;
    ScalaLocalVars localVars;
    boolean isOverride;
    ScalaExpression body;
    
    ScalaMethodInfo(String returnedType, ScalaFormals formalParams, boolean isOverride)
    {
        this.returnedType = returnedType;
        this.formalParams = formalParams;
        this.isOverride = isOverride;
        localVars = new ScalaLocalVars();
    }
    
    String getReturnedType()
    {
        return returnedType;
    }
    
    ScalaFormals getFormalParams()
    {
        return formalParams;
    }
    
    void putLocalVar(String name, ScalaLocalVar info)
    {
        localVars.put(name, info);
    }
    
    ScalaLocalVar getLocalVar(String name)
    {
        return localVars.get(name);
    }
    
    boolean isOverride()
    {
        return isOverride;
    }
    
    void setBody(ScalaExpression body)
    {
        this.body = body;
    }
}

class ScalaAttrInfo implements ScalaFeatureInfo
{
    private String type;
    private ScalaExpression init;
    
    public ScalaAttrInfo(String type)
    {
        this.type = type;
    }
    
    public String getType()
    {
        return type;
    }
    
    void setInit(ScalaExpression init)
    {
        this.init = init;
    }
    
    ScalaExpression getInit()
    {
        return init;
    }
}

class ScalaClassInfo
{
    private ScalaAttributeTable attrTable;
    private ScalaMethodTable methodTable;
    private String baseName;
    private String fileName;
    
    ScalaClassInfo(String baseName, String fileName)
    {
        this.baseName = baseName;
        attrTable = new ScalaAttributeTable();
        methodTable = new ScalaMethodTable();
        this.fileName = fileName;
    }
    
    ScalaClassInfo(
        ScalaAttributeTable attrTable,
        ScalaMethodTable methodTable,
        String baseName,
        String fileName)
    {
        this.attrTable = attrTable;
        this.methodTable = methodTable;
        this.baseName = baseName;
        this.fileName = fileName;
    }
    
    ScalaMethodInfo putMethod(String name, ScalaMethodInfo methodInfo)
    {
        ScalaMethodInfo oldInfo = methodTable.get(name);
        methodTable.put(name, methodInfo);
        return oldInfo;
    }
    
    ScalaAttrInfo putAttribute(String name, ScalaAttrInfo attrInfo)
    {
        ScalaAttrInfo oldInfo = attrTable.get(name);
        attrTable.put(name, attrInfo);
        return oldInfo;
    }
    
    ScalaMethodInfo getMethod(String name)
    {
        return methodTable.get(name);
    }
    
    ScalaAttrInfo getAttribute(String name)
    {
        return attrTable.get(name);
    }
    
    Set<String> getMethodNames()
    {
        return methodTable.keySet();
    }
    
    Set<String> getAttrNames()
    {
        return attrTable.keySet();
    }
    
    String getBaseName()
    {
        return baseName;
    }
    
    String getFilename()
    {
        return fileName;
    }
}

class ScalaFormal
{
    private String name;
    private String type;
    
    ScalaFormal(String name, String type)
    {
        this.name = name;
        this.type = type;
    }
    
    String getName()
    {
        return name;
    }
    
    String getType()
    {
        return type;
    }
}

class ScalaFormals extends LinkedList<ScalaFormal>
{
}

class ScalaLocalVars extends HashMap<String, ScalaLocalVar>
{
}

interface ScalaFeatureInfo
{
}

class ScalaMethodTable extends HashMap<String, ScalaMethodInfo>
{
}

class ScalaAttributeTable extends HashMap<String, ScalaAttrInfo>
{
}

class ScalaClassTable extends HashMap<String, ScalaClassInfo>
{
}

class ScalaClasses extends LinkedList<ScalaClassInfo>
{
}

interface ScalaExpression
{
    String typeName();
}

class ScalaIntConst implements ScalaExpression
{
    int value;
    
    ScalaIntConst(int value)
    {
        this.value = value;
    }
    
    public String typeName()
    {
        return ScalaConstants.IntegerClassname;
    }
}

class ScalaStringConst implements ScalaExpression
{
    String value;

    ScalaStringConst(String value)
    {
        this.value = value;
    }
    
    public String typeName()
    {
        return ScalaConstants.StringClassname;
    }
}

class ScalaBoolConst implements ScalaExpression
{
    boolean value;
    
    ScalaBoolConst(boolean value)
    {
        this.value = value;
    }
    
    public String typeName()
    {
        return ScalaConstants.BooleanClassname;
    }
}

enum IdScope
{
    LOCAL, PARAMETER, CLASS
}

class ScalaObject implements ScalaExpression
{
    String id;
    String type;
    IdScope idScope;
    
    ScalaObject(String id,  IdScope idScope, String type)
    {
        this.id = id;
        this.type = type;
        this.idScope = idScope;
    }
    
    public String typeName()
    {
        return type;
    }
}

class ScalaNew implements ScalaExpression
{
    String type;
    
    ScalaNew(String type)
    {
        this.type = type;
    }
    
    public String typeName()
    {
        return type;
    }
}

class ScalaThis implements ScalaExpression
{
    public String typeName()
    {
        return ScalaConstants.ThisClassName;
    }
}

class ScalaBlock implements ScalaExpression
{
    ScalaExpressionList list;
    
    ScalaBlock(ScalaExpressionList list)
    {
        this.list = list;
    }
    
    public String typeName()
    {
        return list.getLast().typeName();
    }
}

class ScalaIfThenElse implements ScalaExpression
{
    ScalaExpression condition;
    ScalaExpression thenBranch;
    ScalaExpression elseBranch;

    ScalaIfThenElse(ScalaExpression condition, ScalaExpression thenBranch,
                    ScalaExpression elseBranch)
    {
        this.condition = condition;
        this.thenBranch = thenBranch;
        this.elseBranch = elseBranch;
    }
    
    public String typeName()
    {
        return ScalaMapsBuilder.inhGraph.getCommonAncestor(thenBranch.typeName(), elseBranch.typeName());
    }
}

class ScalaWhile implements ScalaExpression
{
    ScalaExpression condition;
    ScalaExpression body;

    ScalaWhile(ScalaExpression condition, ScalaExpression body)
    {
        this.condition = condition;
        this.body = body;
    }
    
    public String typeName()
    {
        return body.typeName();
    }
}

class ScalaMethodCall implements ScalaExpression
{
    ScalaExpression caller;
    String name;
    String type;
    ScalaExpressionList actual;
    int line;
    
    ScalaMethodCall(ScalaExpression caller, String name, String type,
                    ScalaExpressionList actual, int line)
    {
        this.caller = caller;
        this.name = name;
        this.type = type;
        this.actual = actual;
        this.line = line;
    }
    
    public String typeName()
    {
        return type;
    }
    
    int getLine()
    {
        return line;
    }
}

class ScalaBinary implements ScalaExpression
{
    ScalaExpression lhs;
    ScalaExpression rhs;
    ScalaConstants.ScalaBinaryOp op;
    
    ScalaBinary(ScalaExpression lhs, ScalaExpression rhs, ScalaConstants.ScalaBinaryOp op)
    {
        this.lhs = lhs;
        this.rhs = rhs;
        this.op = op;
    }
    
    boolean isRelational()
    {
        switch (op) {
            case LT: case LE: case EQ: case GE: case GT:
                return true;
            default:
                return false;
        }
    }
    
    public String typeName()
    {
        if (isRelational())
            return ScalaConstants.BooleanClassname;
        else
            return ScalaConstants.IntegerClassname;
    }
}

class ScalaUnary implements ScalaExpression
{
    ScalaExpression operand;
    ScalaConstants.ScalaUnaryOp op;
    
    private boolean isLogical()
    {
        switch (op) {
            case NOT:
                return true;
            default:
                return false;
        }
    }
    
    ScalaUnary(ScalaExpression operand, ScalaConstants.ScalaUnaryOp op)
    {
        this.operand = operand;
        this.op = op;
    }
    
    public String typeName()
    {
        if (isLogical())
            return ScalaConstants.BooleanClassname;
        else
            return ScalaConstants.IntegerClassname;
    }
}

class ScalaAssign implements ScalaExpression
{
    String lhs;
    IdScope scope;
    String type;
    ScalaExpression rhs;
    
    ScalaAssign(String lhs, IdScope scope, String type, ScalaExpression rhs)
    {
        this.lhs = lhs;
        this.scope = scope;
        this.type = type;
        this.rhs = rhs;
    }
    
    public String typeName()
    {
        return type;
    }
}

class ScalaLocalVar implements ScalaExpression
{
    String name;
    String type;
    ScalaExpression init;
    
    ScalaLocalVar(String name, String type, ScalaExpression init)
    {
        this.name = name;
        this.type = type;
        this.init = init;
    }
    
    public String typeName()
    {
        return type;
    }
}

class ScalaExpressionList extends LinkedList<ScalaExpression>
{
}
